#include "Camera.hpp"
#include "Keys.hpp"
#include "Device.hpp"

namespace red{
	Camera::Camera() : mName("Camera1"), mPosition(Vector3F::ZERO), mUpdated(true), mSpeed(10.f), mRpm(10.f) {
	}

	Camera::Camera(const Vector3F &Pos) : mPosition(Pos), mUpdated(true), mSpeed(10.f), mRpm(10.f) {
	}

	Camera::~Camera(){
	}
	
	void Camera::Init(Device* pDevice){
		mDevice = pDevice;
	}

	void Camera::OnEvent(sf::Event &pEvent){
		if(pEvent.Type == sf::Event::MouseButtonPressed && pEvent.Key.Code == (sf::Mouse::Button)RightButton){
			// sauvegarde de l'endroit du clic
			mCursorPos.x = mDevice->GetInputs().GetMouseX();
			mCursorPos.y = mDevice->GetInputs().GetMouseY();
			// on cache le curseur
			mDevice->GetWindow().ShowCursor(false);
			// deplacement de la souris au milieu de la fenetre
			mDevice->GetWindow().SetCursorPosition(mDevice->GetWindow().GetHalfWidth(), mDevice->GetWindow().GetHalfHeight());
		}
		
		if(pEvent.Type == sf::Event::MouseButtonReleased && pEvent.Key.Code == (sf::Mouse::Button)RightButton){
			// On replace la souris a l'emplacement du click
			mDevice->GetWindow().SetCursorPosition(mCursorPos.x, mCursorPos.y);
			// on remontre le curseur
			mDevice->GetWindow().ShowCursor(true);
		}
	}

	void Camera::HandleInputs(){
		Input& input = mDevice->GetInputs();
		f32 elapsedTime = mDevice->GetFrameTime();
		bool movement = false;

		// Vitesse de la camera
		if(input.IsKeyDown(LShift))	SetSpeed(30);
		else						SetSpeed(10);

		// Mouvement de la camera
		if(input.IsKeyDown(Z)) {MoveForward(1.f * elapsedTime); movement = true; }
		if(input.IsKeyDown(S)) {MoveForward(-1.f * elapsedTime); movement = true; }
		if(input.IsKeyDown(D)) {MoveRight(1.f * elapsedTime); movement = true; }
		if(input.IsKeyDown(Q)) {MoveRight(-1.f * elapsedTime); movement = true; }
		if(input.IsKeyDown(A)) {MoveUp(1.f * elapsedTime); movement = true; }
		if(input.IsKeyDown(E)) {MoveUp(-1.f * elapsedTime); movement = true; }

		// Orientation de la camera
		if(input.IsMouseDown(RightButton)){
			// on recupere les coordonnees du deplacement
			mCursorPos.x = input.GetMouseX();
			mCursorPos.y = input.GetMouseY();
		
			// on bouge la camera en fonction de ce mouvement
			Yaw(  (mCursorPos.x - mDevice->GetWindow().GetHalfWidth())  * elapsedTime);
			Pitch((mCursorPos.y - mDevice->GetWindow().GetHalfHeight()) * elapsedTime);

			// On replace le curseur au point 
			mDevice->GetWindow().SetCursorPosition(mDevice->GetWindow().GetHalfWidth(), mDevice->GetWindow().GetHalfHeight());
			movement = true;
		}
	}

	void Camera::SetPosition(const Vector3F &Pos){
		mPosition = Pos;
		Update();
	}
	
	void Camera::SetOrientation(const Quaternion &Quat){
		mOrientation = Quat;
		Update();
	}
	
	const Vector3F Camera::GetAxisX() const{
		Vector3F ret;
		ret.x = ViewMatrix.a11;
		ret.y = ViewMatrix.a21;
		ret.z = ViewMatrix.a31;

		return ret;
	}

	const Vector3F Camera::GetAxisY() const{
		Vector3F ret;
		ret.x = ViewMatrix.a12;
		ret.y = ViewMatrix.a22;
		ret.z = ViewMatrix.a32;

		return ret;
	}

	const Vector3F Camera::GetAxisZ() const{
		Vector3F ret;
		ret.x = ViewMatrix.a13;
		ret.y = ViewMatrix.a23;
		ret.z = ViewMatrix.a33;

		return ret;
	}
	/*
	const Matrix4& Camera::GetViewMatrix(){
		if(mNeedUpdate)
			Update();

		return ViewMatrix;
	}
	*/
	void Camera::Update(){
		Matrix4 matTranslation, matRotation;

		// Calcul de la nouvelle matrice de vue
		matTranslation.SetTranslation(-mPosition.x, -mPosition.y, -mPosition.z); 
		matRotation = mOrientation.ToMatrix();

		ViewMatrix = matTranslation * matRotation;

		// Envoi de la matrice au renderer pour calcul de la ViewProjMatrix
		mDevice->GetRenderer().Set3DViewMatrix(ViewMatrix);

		// Recuperation de la ViewProjMatrix pour calculer le nouveau Frustum
		mFrustum.SetFrustum(mDevice->GetRenderer().Get3DViewProjMatrix());

		//mNeedUpdate = false;
		mUpdated = true;
	}

	void Camera::LookAt(const Vector3F &To, const Vector3F &From){
		Matrix4 temp;
		temp.LookAt(From, To, Vector3F::UNIT_Y);

		mPosition = From;
		mOrientation.FromMatrix(temp);

		Update();
	}

	void Camera::ApplyTranslation(f32 dist, eCamDir dir){
		Vector3F direction;

		switch(dir){
			case CD_Forward	: direction = -GetAxisZ(); break;
			case CD_Up		: direction = GetAxisY(); break;
			case CD_Right	: direction = GetAxisX(); break;
		}

		mPosition += direction * mSpeed * dist;
		Update();
	}

	void Camera::ApplyRotation(f32 angle, eCamRot rot){
		angle *= (-mRpm / 60.f);		// multiplie par la vitesse de rotation

		switch(rot){
			case CR_Yaw 	: RotateYAxis(angle); break;
			case CR_Pitch 	: RotateXAxis(angle); break;
			case CR_Roll 	: RotateZAxis(angle); break;
		}

		mOrientation.Normalize();
		Update();
	}

	void Camera::RotateXAxis(f32 angle){
		Quaternion temp;
		temp.FromAxisAngle(Vector3F::UNIT_X, angle);

		mOrientation *= temp;
	}

	void Camera::RotateYAxis(f32 angle){
		Quaternion temp;
		temp.FromAxisAngle(Vector3F::UNIT_Y, angle);

		mOrientation = temp * mOrientation;		// (Autour de UNIT_Y Global et non local)
	}

	void Camera::RotateZAxis(f32 angle){
		Quaternion temp;
		temp.FromAxisAngle(Vector3F::UNIT_Z, angle);

		mOrientation *= temp;
	}

}
